﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Reactive.Linq;
using System.Text;
using MiscUtil;
using MiscUtil.Conversion;
using MiscUtil.IO;
using TlvLib.PacketParsers;

namespace TlvLib
{
    public delegate void RawPacketHandler(object sender, RawPacketReceivedEventArgs args);

    public delegate void StatusReportHandler(object sender, MiscUtil.EventArgs<string> args);

    public class PacketReader
    {
        private readonly TcpClient _client;

        private const int Headerlen = 4;


        private TlvPacker _packer = new TlvPacker();
        private object _sendLock = new object();
        private PacketParsers.TimeSyncPacker _timeSyncPacker;
        private static NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();
        private IDisposable _heartBeatTimerObserver;
        private IDisposable _timeSyncTimerObserver;
        private EndPoint _remoteEndpoint;


        public bool AutoReconnect { get; set; }
        public event RawPacketHandler RawPacketReceived;
        public event StatusReportHandler StatusReport;

        public void OnStatusReport(string status)
        {
            StatusReportHandler handler = StatusReport;
            if (handler != null) handler(this, new EventArgs<string>(status));
        }

        private void RaiseRawPacketReceived(RawPacketReceivedEventArgs args)
        {
            RawPacketHandler handler = RawPacketReceived;
            if (handler != null) handler(this, args);
        }

        public PacketReader(TcpClient tcpClient)
        {
            if (tcpClient == null) throw new ArgumentNullException("tcpClient");
            _client = tcpClient;

            _remoteEndpoint = tcpClient.Client.RemoteEndPoint;

            _timeSyncPacker = new TimeSyncPacker();

            InitTimers();
        }

        private void InitTimers()
        {
            var heartBeatTimer = Observable.Interval(TimeSpan.FromSeconds(10));
            _heartBeatTimerObserver = heartBeatTimer.Subscribe(i => SendHeartBeat());

            var timeSyncTimer = Observable.Interval(TimeSpan.FromMinutes(1));
            _timeSyncTimerObserver = timeSyncTimer.Subscribe(i => SendTimeSync());
        }

        private void SendTimeSync()
        {
            var t = _timeSyncPacker.Pack(DateTime.Now);
            var sync = new RawPacket()
                           {
                               IsRequest = true,
                               MessageId = PacketType.TimeSync,
                               Payload = t
                           };
            Send(sync);
        }

        private void SendHeartBeat()
        {
            var heartBeat = new RawPacket() { IsRequest = true, MessageId = PacketType.HeartBeat };
            Send(heartBeat);
        }

        public void Start()
        {
            BeginRead();
        }

        public void Stop()
        {
            AutoReconnect = false;
            CleanUp();
        }

        public void Send(RawPacket packet)
        {
            lock (_sendLock)
            {
                try
                {
                    if (_client != null && _client.Connected)
                    {
                        var buffer = _packer.Pack(packet);

                        _client.Client.Send(buffer);
                        _client.GetStream().Flush();

                    }
                }
                catch
                {
                    CleanUp();
                }
            }
        }



        private void CleanUp()
        {
            if (_heartBeatTimerObserver != null)
            {
                _heartBeatTimerObserver.Dispose();
            }

            if (_timeSyncTimerObserver != null)
            {
                _timeSyncTimerObserver.Dispose();
            }

            if (_client != null)
            {
                _client.Close();
            }

        }



        private void ReportConnectStatus(string status)
        {
            var ipEndpoint = (IPEndPoint) _remoteEndpoint;
            var msg = string.Format("连接 {0}:{1} {2}", ipEndpoint.Address, ipEndpoint.Port, status);
            OnStatusReport(msg);
        }

        private void BeginRead()
        {

            SendTimeSync();

            var stateObject = new StateObject();

            try
            {
                _client.GetStream().BeginRead(
               stateObject.Header,
               stateObject.HeaderBytesCount,
               stateObject.Header.Length - stateObject.HeaderBytesCount,
               ReadCallback, stateObject);
            }
            catch (Exception)
            {
                CleanUp();
            }


        }

        private void ReadCallback(IAsyncResult ar)
        {
            int bytesRead = 0;
            NetworkStream stream;
            var state = (StateObject)ar.AsyncState;
            try
            {
                stream = _client.GetStream();
                bytesRead = stream.EndRead(ar);
                if (bytesRead == 0)
                {
                    ReportConnectStatus("关闭连接");
                    CleanUp();
                    return;
                }

            }
            catch
            {
                ReportConnectStatus("中断");
                CleanUp();
                return;
            }

            if (state.IsReadingHeader)
            {
                state.HeaderBytesCount += bytesRead;
                if (state.HeaderBytesCount == state.Header.Length)
                {
                    state.IsReadingHeader = false;
                    var i = MiscUtil.Conversion.EndianBitConverter.Big.ToUInt32(state.Header, 4);
                    if (i > Constants.MaxPacketSize) //超长数据包
                    {
                        var msg = "收到超长数据包, 断开连接";
                        _logger.Fatal(msg, i);
                        ReportConnectStatus(msg);
                        CleanUp();
                        return;
                    }
                    System.Diagnostics.Debug.WriteLine("packet len {0}", i);
                    state.Value = new byte[i];
                }
            }
            else
            {
                state.ValueBytesCount += bytesRead;
                if (state.Value.Length == state.ValueBytesCount)
                {
                    var i = BitConverter.ToInt16(state.Header, 2);
                    var j = System.Net.IPAddress.NetworkToHostOrder(i);
                    var p = new RawPacket()
                                {
                                    MessageId = j,
                                    Payload = state.Value
                                };

                    //ReportConnectStatus("收到数据包(1000-车牌，1003-连接状态，1007-校时回应), 包类型：" + j);
                    try
                    {
                        RaiseRawPacketReceived(new RawPacketReceivedEventArgs(p));
                    }
                    catch (Exception ex)
                    {
                        var msg = "处理数据包时发生异常：" + ex.Message;
                        ReportConnectStatus(msg);
                    }

                    state = new StateObject();
                    state.IsReadingHeader = true;
                }
            }

            try
            {
                if (state.IsReadingHeader)
                {
                    stream.BeginRead(
                                    state.Header,
                                    state.HeaderBytesCount,
                                    state.Header.Length - state.HeaderBytesCount,
                                    ReadCallback,
                                    state);
                }
                else
                {
                    stream.BeginRead(
                                    state.Value,
                                    state.ValueBytesCount,
                                    state.Value.Length - state.ValueBytesCount,
                                    ReadCallback,
                                    state);
                }
            }
            catch
            {
                ReportConnectStatus("中断");
                CleanUp();
            }

        }
    }


    class StateObject
    {
        public bool IsReadingHeader = true;

        public const int HeaderBytesLen = 8;
        public int HeaderBytesCount;
        public byte[] Header = new byte[HeaderBytesLen];


        public int ValueBytesCount;
        public byte[] Value;
    }


}

